package com.mars.seckill.service;

import com.alibaba.fastjson.JSON;
import com.mars.seckill.entity.Order;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@Service
public class BuyServiceImpl implements BuyService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 限流方法：所谓限流，意思是防止在短时间内出现大量请求
     * 该方法达到的效果是，在limitTime的时间内，最多允许limitNum的请求进入
     * @param item
     * @param limitTime
     * @param limitNum
     * @return
     */

    @Override
    public boolean canvisit(String item, int limitTime, int limitNum) {

        long curTime = System.currentTimeMillis();

        // 计算剩余数量
        // 所谓剩余数量，是指到目前最新的请求过来时为止，宽度为limitTime的时间窗口内的请求数量
        long count = redisTemplate.opsForZSet().zCard(item);
        // 如果时间窗口内的数量还不到限流上限，就将当前记录缓存，同时设置过期时间
        if (count < limitNum) {
            // 以当前时间作为值，加入集合，同时将权重也设为当前时间
            redisTemplate.opsForZSet().add(item, curTime, curTime);

            // 设置过期时间为一个时间窗口，这样就能保证距离最新请求一个时间窗口的请求自动过期，从而保证限流方法的有效性
            redisTemplate.expire(item, limitTime, TimeUnit.SECONDS);
            return true;

        } else
            return false;
    }

    /**
     * 为什么要用lua脚本 --> lua可以驻留在内存中，在高并发的场景下，可以提高性能？
     *
     * @param item
     * @param person
     * @return
     */

    @Override
    public String buy(String item, String person) {
        // 设定lua脚本
        String luaScript = "local person = ARGV[1] \n" +
                "local item = KEYS[1] \n" +
                // tonumber 转为数值
                "local left = tonumber(redis.call('get',item)) \n" +
                "if (left >= 1) \n" +
                "then redis.call('decrby',item,1) \n" +
                "redis.call('rpush','personList',person) \n" +
                "return 1 \n" +
                "else \n" +
                "return 0 \n" +
                "end \n" +
                " \n";

        String key = item;
        String args = person;
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(luaScript);
        // 调用lua脚本
        // 返回值是Long类型的
        Object luaResult = redisTemplate.execute((RedisConnection connection) -> connection.eval(
                redisScript.getScriptAsString().getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                args.getBytes()));

        //System.out.println("=========");
        //System.out.println(luaResult instanceof Long);

        // 如果结果是1，则秒杀成功，将秒杀成功的记录发送到rabbitmq中
        if ((Long) luaResult > 0) {
            Order order = new Order(item, person);
            rabbitTemplate.convertAndSend("objectExchange", "objectQueue", JSON.toJSONString(order));
        }

        return luaResult.toString();

    }

}
